﻿using System;
using smp.ButterflyCakes.Models;
using smp.ButterflyCakes.Models.Data;
using smp.ButterflyCakes.Models.FormModels.Order;
using smp.ButterflyCakes.Models.InputModels.Order;
using smp.ButterflyCakes.Repository.Databases;
using smp.ButterflyCakes.Repository.Interfaces;
using smp.QueryDb;
using smp.QueryDb.Executing;
using smp.QueryDb.Structure;

namespace smp.ButterflyCakes.Repository.Implementation
{
    public class DomainCakeRepository : IDomainCakeRepository
    {
        private readonly IDbSessionFactory _sessionFactory;

        public DomainCakeRepository()
        {
            _sessionFactory = new DbSessionFactory("ButterflyCakes");
        }

        public DomainCakeRepository(IDbSessionFactory sessionFactory)
        {
            _sessionFactory = sessionFactory;
        }

        #region Implementation of IDomainCakeRepository

        public CakeDataItem GetCakeData(Guid id)
        {
            using (var session = _sessionFactory.Create())
            {
                var model = session.Load(new Query<CakeDataItem>()
                                             .From(Db.Component
                                                       .Join(new Join(Db.Order)
                                                                 .On(Component.OrderId)
                                                                 .IsEqualTo(Order.Id)
                                                                 .SubJoin(new Join(Db.EntityStage)
                                                                              .On(Order.CurrentLastStageId)
                                                                              .IsEqualTo(EntityStage.Id))))
                                             .Map(x => x.Id).To(Component.Id)
                                             .Map(x => x.OrderId).To(Component.OrderId)
                                             .Map(x => x.Type).To(Component.ComponentType)
                                             .Map(x => x.IsCarved).To(Component.IsCarved)
                                             .Map(x => x.NumberOfFlavours).To(Component.NumberOfFlavours)
                                             .Map(x => x.LastStage).ToNested(new NestedMap<NextOrderStage>()
                                                                                 .Map(x => x.Id).To(EntityStage.Id)
                                                                                 .Map(x => x.Title).To(EntityStage.Title)
                                                                                 .Map(x => x.Stage).To(EntityStage.Stage)
                                                                                 .Map(x => x.OrderId).To(EntityStage.OrderId)
                                                                                 .Map(x => x.Created).To(EntityStage.Created)
                                                                                 .Map(x => x.Index).To(EntityStage.Index))
                                             .Where(new Filter<CakeDataItem>(Component.Id).Is.EqualTo(id)));
                
                var flavours = session.Query(new Query()
                                                 .From(Db.ComponentFlavour)
                                                 .Select(ComponentFlavour.CakeFlavourId)
                                                 .OrderBy(ComponentFlavour.CakeFlavourId)
                                                 .TakePage(1).ForPageSize(1)
                                                 .Where(new Filter(ComponentFlavour.ComponentId).Is.EqualTo(id)));
                if (flavours != null)
                {
                    model.ActualNumberOfFlavours = flavours.TotalRecords;
                }

                return model;
            }
        }

        public bool SaveCakeTitle(CakeTitleModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create(true))
            {
                if (session.Update(new Update(Db.Component)
                    .Set(Component.IsCarved).To(model.IsCarved)
                    .Set(Component.Title).To(model.Title)
                    .Set(Component.ComponentType).To(model.Type)
                    .Set(Component.NumberOfFlavours).To(model.NumberOfFlavours)
                    .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId))) != 1)
                    return false;

                if (!CreateStageAndUpdateOrder(nextStage, session))
                    return false;

                session.CommitTransaction();
                return true;
            }
        }

        public bool AddCakeFlavour(AddFlavourInput model)
        {
            using (var session = _sessionFactory.Create())
            {
                return session.Insert(new Insert(Db.ComponentFlavour)
                                          .Set(ComponentFlavour.Id).To(Guid.NewGuid())
                                          .Set(ComponentFlavour.ComponentId).To(model.ComponentId)
                                          .Set(ComponentFlavour.CakeFlavourId).To(model.FlavourId)
                                          .Set(ComponentFlavour.CakeFlavourTypeId).To(model.FlavourTypeId)) == 1;
            }
        }

        public bool RemoveCakeFlavour(RemoveFlavourInput model)
        {
            using (var session = _sessionFactory.Create())
            {
                var result = session.Delete(new Delete(Db.ComponentFlavour)
                                          .Where(new Filter(ComponentFlavour.ComponentId).Is.EqualTo(model.ComponentId)
                                                     .And(ComponentFlavour.CakeFlavourId).Is.EqualTo(model.FlavourId)));
                return result == 1;
            }
        }

        public bool SaveCakeFlavour(ComponentFlavourModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create())
            {
                return CreateStageAndUpdateOrder(nextStage, session);
            }
        }

        public bool SaveCakeQuantity(ComponentQuantityModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create())
            {
                session.StartTransaction();

                if(session.Update(new Update(Db.Component)
                    .Set(Component.Quantity).To(model.Quantity)
                    .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId))) != 1)
                    return false;

                if (!CreateStageAndUpdateOrder(nextStage, session))
                    return false;

                session.CommitTransaction();
                return true;
            }
        }
        
        public bool SaveCakeHeight(ComponentHeightModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create())
            {
                session.StartTransaction();

                if (session.Update(new Update(Db.Component)
                    .Set(Component.Height).To(model.Height)
                    .Set(Component.HeightType).To(model.HeightType)
                    .Set(Component.NumberOfFlavours).To(model.NumberOfFlavours)
                    .Set(Component.NumberOfStripedCakes).To(model.NumberOfCakes)
                    .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId))) != 1)
                    return false;

                if (!CreateStageAndUpdateOrder(nextStage, session))
                    return false;

                session.CommitTransaction();
                return true;
            }
        }

        public bool SaveCakeSize(SaveSizeInput model)
        {
            if(!model.SizeId.HasValue)
                return false;
            using (var session = _sessionFactory.Create())
            {
                if (model.IsNew.HasValue && model.IsNew.Value)
                {
                    return session.Insert(new Insert(Db.ComponentSize)
                                              .Set(ComponentSize.ComponentId).To(model.ComponentId)
                                              .Set(ComponentSize.CustomShapeDetail).To(model.CustomDetail)
                                              .Set(ComponentSize.Id).To(model.SizeId.Value)
                                              .Set(ComponentSize.Portions).To(model.Portions)
                                              .Set(ComponentSize.Shape).To(model.Shape)
                                              .Set(ComponentSize.Size).To(model.Size)
                                              .Set(ComponentSize.Width).To(model.Width)) == 1;
                }

                return session.Update(new Update(Db.ComponentSize)
                                          .Set(ComponentSize.ComponentId).To(model.ComponentId)
                                          .Set(ComponentSize.CustomShapeDetail).To(model.CustomDetail)
                                          .Set(ComponentSize.Portions).To(model.Portions)
                                          .Set(ComponentSize.Shape).To(model.Shape)
                                          .Set(ComponentSize.Size).To(model.Size)
                                          .Set(ComponentSize.Width).To(model.Width)
                                          .Where(new Filter(ComponentSize.Id).Is.EqualTo(model.SizeId.Value))) == 1;
            }
        }

        public bool RemoveCakeSize(RemoveSizeInput model)
        {
            using (var session = _sessionFactory.Create())
            {
                return session.Delete(new Delete(Db.ComponentSize)
                                          .Where(new Filter(ComponentSize.Id).Is.EqualTo(model.SizeId))) == 1;
            }
        }

        public bool SetCakeSize(ComponentSizeModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create())
            {
                return CreateStageAndUpdateOrder(nextStage, session);
            }
        }

        public bool SaveCakeButtercream(ComponentButterCreamModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create())
            {
                session.StartTransaction();

                session.Update(new Update(Db.Component)
                                   .Set(Component.ButtercreamId).To(model.ButterCreamId)
                                   .Set(Component.GanacheId).To(model.GanacheId)
                                   .Set(Component.FillingColour).To(model.ButterCreamColour)
                                   .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId)));

                if (!CreateStageAndUpdateOrder(nextStage, session))
                    return false;

                session.CommitTransaction();
                return true;
            }
        }

        public bool SaveCakePreserve(ComponentPreserveModel model, StageDataItem nextStage)
        {
            using (var session = _sessionFactory.Create())
            {
                session.StartTransaction();

                session.Update(new Update(Db.Component)
                                   .Set(Component.PreserveId).To(model.PreserveId)
                                   .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId)));

                if (!CreateStageAndUpdateOrder(nextStage, session))
                    return false;

                session.CommitTransaction();
                return true;
            }
        }

        public bool SaveShapeBuilderToCake(ShapeBuilderInput model, DiagramItem diagram)
        {
            using(var session = _sessionFactory.Create())
            {
                session.StartTransaction();

                var componentTitleResult = session.Load(new Query()
                                                     .From(Db.Component)
                                                     .Select(Component.Title)
                                                     .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId)));
                diagram.Description = componentTitleResult.Value("Title") + " shape builder diagram.";

                session.Delete(new Delete(Db.Diagram)
                                   .Where(new Filter(Diagram.ComponentId).Is.EqualTo(model.ComponentId)));

                if(session.Insert(new Insert(Db.Diagram)
                    .Set(Diagram.Id).To(diagram.Id)
                    .Set(Diagram.ComponentId).To(diagram.ComponentId)
                    .Set(Diagram.OrderId).To(diagram.OrderId)
                    .Set(Diagram.Description).To(diagram.Description)
                    .Set(Diagram.Html).To(diagram.Html)) != 1)
                    return false;

                if(session.Update(new Update(Db.Component)
                    .Set(Component.ShapeBuilderActiveCellString).To(model.SelectedCellString)
                    .Set(Component.Portions).To(model.Portions)
                    .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId))) != 1)
                    return false;

                session.Delete(new Delete(Db.ComponentSize)
                                   .Where(new Filter(ComponentSize.ComponentId).Is.EqualTo(model.ComponentId)));

                foreach(var bake in model.Bakes)
                {
                    if(session.Insert(new Insert(Db.ComponentSize)
                        .Set(ComponentSize.Shape).To(CakeShape.Rectangle)
                        .Set(ComponentSize.ComponentId).To(model.ComponentId)
                        .Set(ComponentSize.Size).To(bake.Length)
                        .Set(ComponentSize.Width).To(bake.Width)
                        .Set(ComponentSize.Id).To(Guid.NewGuid())) != 1)
                        return false;
                }

                session.CommitTransaction();
                return true;
            }
        }

        public bool SaveFinish(ComponentFinishingModel model, StageDataItem nextStage)
        {
            using(var session = _sessionFactory.Create())
            {
                session.StartTransaction();

                if(session.Update(new Update(Db.Component)
                    .Set(Component.FinishType).To(model.FinishType)
                    .Set(Component.CoveringType).To(model.CoveringType)
                    .Set(Component.CoveringColour).To(model.FinishColour)
                    .Where(new Filter(Component.Id).Is.EqualTo(model.ComponentId))) != 1)
                    return false;

                if(!CreateStageAndUpdateOrder(nextStage, session))
                    return false;

                session.CommitTransaction();
                return true;
            }
        }

        #endregion

        private bool CreateStageAndUpdateOrder(StageDataItem stage, IDbSession session)
        {
            if (stage == null)
                return true;

            if (!stage.IsExisting)
            {
                if (session.Insert(new Insert(Db.EntityStage)
                                      .Set(EntityStage.Id).To(stage.Id)
                                      .Set(EntityStage.Created).To(stage.Created)
                                      .Set(EntityStage.Stage).To(stage.Stage)
                                      .Set(EntityStage.Title).To(stage.Title)
                                      .Set(EntityStage.ComponentId).To(stage.ComponentId)
                                      .Set(EntityStage.Index).To(stage.Index)
                                      .Set(EntityStage.OrderId).To(stage.OrderId)
                                      .Set(EntityStage.PreviousStageId).To(stage.PreviousStageId)) != 1)
                    return false;
            }


            if (session.Update(new Update(Db.Order)
                .Set(Order.CurrentLastStageId).To(stage.Id)
                .Where(new Filter(Order.Id).Is.EqualTo(stage.OrderId))) != 1)
                return false;

            return true;
        }
    }
}
